#include "FastSPI_LED2.h"    // fastspiライブラリの定義インクルード　https://code.google.com/p/fastspi/
#include "FlexiTimer2.h"     // flexitimerライブラリの定義インクルード  https://github.com/wimleers/flexitimer2
#include <Bounce.h>          // bounceライブラリの定義インクルード（このライブラリは標準で入っている）
#include <Wire.h>            // I2C LCD用にI2Cライブラリの定義をインクルード（このライブラリは標準で入っている）
#include <stdio.h>           // 標準ライブラリ、特に数字から文字列への変換で面倒な操作を簡略化するためにsprintfを使う

#include "config.h"          // 設定の定義インクルード

///////////////////////////////////////////////////////////////////////////////////////////
//
// 5桁（になる予定）の大型表示ストップウォッチを作る
// 

// LEDの配列。1データが1つのLEDに対応する。
CRGB leds[NUM_LEDS];

int sevenseg[10][SEGMENTS] = {  // 7セグLEDの発光位置定義
  {1,1,1,1,1,1,0},  // 0
  {1,1,0,0,0,0,0},  // 1
  {0,1,1,0,1,1,1},  // 2
  {1,1,1,0,0,1,1},  // 3
  {1,1,0,1,0,0,1},  // 4
  {1,0,1,1,0,1,1},  // 5
  {1,0,1,1,1,1,1},  // 6
  {1,1,1,0,0,0,0},  // 7
  {1,1,1,1,1,1,1},  // 8
  {1,1,1,1,0,1,1} };// 9

// スイッチのチャタリング防止
Bounce startstopsw = Bounce(STARTSTOPSW, 10); // スタートストップスイッチのピン定義と10m秒のチャタリング防止時間
Bounce resetsw = Bounce(RESETSW, 10);         // リセットスイッチのピン定義と10m秒のチャタリング防止時間
Bounce basesw = Bounce(BASESW, 10);           // カウント単位設定スイッチのピン定義と10m秒のチャタリング防止時間

// unsigned long counts : ベースカウント
unsigned long counts;

// COUNT構造体は
// int sec10m : 10m秒, sec1 : 秒, min1 : 分, hour1 : 時
// 共有体は int digit[0] .. digit34]
struct COUNTER {
  union {
    struct { int sec10m, sec1, min1, hour1; };
    int digit[4];
  };
};

// COUNT構造体でnow（現在のカウント値）とlast（一回前のカウント値）、ラップ値を宣言
COUNTER now, last, rap;

// int countbase : カウント単位　0 : 10m秒、1 : 100m秒、2 : 1秒
int countbase = 0;

// UPDATE構造体はboolean sec10m : 10m秒更新あり, sec1 : 秒更新あり, min1 : 分更新あり, hour1 : 時更新あり
// 共有体は boolean digit[0] .. digit[3]
struct UPDATE {
  union {
    struct { boolean sec10m, sec1, min1, hour1; };
    boolean digit[3];
  };
};

// UPDATE構造体でupdateFを宣言
UPDATE updateF;

// boolean Running : ストップウォッチの動作状態
boolean Running = false;  // 起動時は停止状態

// boolean raphold : ラップ状態
boolean raphold = false;

// 時刻カウントアップ関数（タイマーが設定時間に達すると呼ばれる）
void countup() {
  now.sec10m = counts % 100;                 // 10m秒のカウントはカウント値を100で割った余り
  now.sec1 = (counts / 100) % 60;            // 1秒のカウントはカウント値を100で割って、更に60で割った余り
  now.min1 = (counts / 100 / 60) % 60;       // 分のカウントはカウント値を6000で割って、更に60で割った余り
  now.hour1 = (counts / 100 / 60 / 60) % 24; // 時のカウントはカウント値を360000で割って、更に24で割った余り
  counts++;                            // カウント値を進める
  updateF.sec10m = true;                                          // 10m秒の更新はこの関数を呼び出した時は常時正
  if (now.sec10m == 0 && last.sec10m == 99) updateF.sec1 = true;  // 秒の更新は10m秒がゼロ、かつ前回10m秒が99だった時に正
  if (now.sec1 == 0 && last.sec1 == 59) updateF.min1 = true;      // 分の更新は秒がゼロ、かつ前回秒が59だった時に正
  if (now.min1 == 0 && last.min1 == 59) updateF.hour1 = true;     // 時の更新は分がゼロ、かつ前回分が59だった時に正
  for (int j=0; j < 4; j++)
    last.digit[j] = now.digit[j];    // 次回に向けて今回の値を保存
}

// 時刻かウントアップのリセット関数
void countreset() {
  counts = 0;          // カウント値をゼロにする
  for (int i; i < 4; i++) {            // 5桁分
    now.digit[i] = last.digit[i] = 0;  // 現在値と過去値をゼロにする
    updateF.digit[i] = false;          // 更新フラグを更新なしにする
  }
}
  
// LED接続の初期化 setup()は起動時1回のみ走る
void setup() {
	// 安全のための確認時間。LEDが燃え出したらすぐ電源を切る！2秒間の猶予時間
//  delay(2000);  // 火入れして接続確認後は不要なので、コメントアウト

      // 接続しているLEDの型名に応じて1行のみコメントから外す。
      // FastLED.addLeds<TM1803, DATA_PIN, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<TM1804, DATA_PIN, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<TM1809, DATA_PIN, RGB>(leds, NUM_LEDS);
      //FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
       FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<UCS1903, DATA_PIN, RGB>(leds, NUM_LEDS);

      // FastLED.addLeds<WS2801, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<SM16716, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<LPD8806, RGB>(leds, NUM_LEDS);

      // FastLED.addLeds<WS2801, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<SM16716, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);
      // FastLED.addLeds<LPD8806, DATA_PIN, CLOCK_PIN, RGB>(leds, NUM_LEDS);

  FlexiTimer2::set(BASETIME, countup);   // タイマー設定（ストップウォッチ設定）
  
  pinMode(STARTSTOPSW, INPUT);      // スタートストップスイッチの接続ピンを入力に設定
  digitalWrite(STARTSTOPSW, HIGH);  // 内部プルアップ抵抗を有効にする
  pinMode(RESETSW, INPUT);          // リセットスイッチの接続ピンを入力に設定
  digitalWrite(RESETSW, HIGH);      // 内部プルアップ抵抗を有効にする
  pinMode(BASESW, INPUT);           // カウント単位設定スイッチの接続ピンを入力に設定
  digitalWrite(BASESW, HIGH);       // 内部プルアップ抵抗を有効にする
  
  lcd_init();                // 液晶表示の初期化
  lcd_clear();               // 表示クリア
  lcd_setline(1);            // カーソルを1行目に
  lcd_puts("Setup done");   // ”Setup done"と表示

}

// LEDの色
int r = 0, g = 0, b = 0;

// 起動時、最初だけ表示するためのフラグ
boolean initial = true;

// 繰り返し実行する関数 setup()の後に繰り返し実行される
void loop() {

  int now1;                    // 表示する桁の仮変数（将来は方式変更予定）
  int leddisp[5];              // LEDに表示する数字（-1は表示なし）
  boolean resetflag = false;  // 表示更新をするためのリセットスイッチを押した事を示すフラグ
//  char line1[16] = "SW:            ";  // LCDの1行目のバッファ

  boolean startstopChange = startstopsw.update();  // スイッチ入力の更新確認
  boolean resetChange = resetsw.update();
  boolean baseChange = basesw.update();

  r = analogRead(RINPUT);    // RのVR電圧読み込み
  g = analogRead(GINPUT);    // GのVR電圧読み込み
  b = analogRead(BINPUT);    // BのVR電圧読み込み
  
  if (basesw.fallingEdge()) {                 // もしカウント単位設定スイッチが押されれば
    if (Running) {                            // 内部タイマーが動作中ならば
      FlexiTimer2::stop(); Running = false;   // 内部タイマーを停止し、動作状態は停止にする
    }
    countreset();                             // カウントをリセット
    lcd_setline(2);                           // カーソルを2行目に
    lcd_puts("SW:Stop...      ");             // "Stop...    "と表示
    
    countbase++;                              // カウント単位を増やす
    if (countbase > 2) countbase = 0;         // 範囲オーバーしたら、最初に戻す
    resetflag = true;
  }
  
  if (startstopsw.fallingEdge())              // もしスタートストップスイッチが押されれば
    if (Running) {                            // 内部タイマーが動作中ならば
      FlexiTimer2::stop(); Running = false;   // 内部タイマーを停止し、動作状態は停止にする
      lcd_setline(2);                         // カーソルを2行目に
      lcd_puts("SW:Stop...      ");           // "Stop...    "と表示
    }
    else                                      // 内部タイマーが停止中
    {
      FlexiTimer2::start(); Running = true;   // 内部タイマーを起動し、動作状態は起動にする
      lcd_setline(2);                         // カーソルを2行目に
      lcd_puts("SW:Counting...  ");           // "Counting..."と表示
    }
  
  if (resetsw.fallingEdge()) {  // もしリセットスイッチが押されれば
      if (Running) {              // 内部タイマーが動作中ならば
        if (raphold == false) {         // ラップ保持状態でなければ
          for ( int i = 0; i < 4; i++ )
            rap.digit[i] = now.digit[i];  // 現在値をラップ値にコピー
          raphold = true;                 // ラップ保持状態
        }
        else
          raphold = false;                // ラップ保持解除
      }
      else {                            // 内部タイマーが停止中
        raphold = false;                // ラップ保持解除
        countreset();                   // カウントをリセット
        lcd_setline(2);                 // カーソルを2行目に
        lcd_puts("SW:Reset        ");   // "Reset      "と表示
      }

      resetflag = true;         // 表示更新のためにリセットした事を示すフラグを立てる
  }
    
  if (updateF.sec10m || resetflag || initial) {    // 10m秒毎の更新か、リセットスイッチによる更新、起動時最初ならば
//    now1 = now.sec10m / 10;                     // 1桁表示なので表示桁を仮変数に代入
    now1 = now.sec1 % 10;                     // 1桁表示なので表示桁を仮変数に代入

    
    char buf[17];                                           // LCD出力バッファを宣言
    switch (countbase) {                                    // カウント単位により切替
      case 0:                                               // カウント単位が10m秒の時   
        if (raphold == false) {                             // ラップ状態でなければ
          sprintf(buf, "%1d:%02d.%02d         ",            // ラップ無しの表示
            now.digit[2] % 10, now.digit[1], now.digit[0]); // 表示は分、秒、10m秒
        } else {                                            // ラップ状態ならば
          sprintf(buf, "%1d:%02d.%02d %1d:%02d.%02d ",      // ラップ付の表示
            now.digit[2] % 10, now.digit[1], now.digit[0],
            rap.digit[2] % 10, rap.digit[1], rap.digit[0]);
        }
        break;
      case 1:                                               // カウント単位が100m秒の時
        if (raphold == false) {
          sprintf(buf, "%02d:%02d.%01d         ",            // 表示は分、秒、100m秒
            now.digit[2], now.digit[1], now.digit[0] / 10);
        } else {
          sprintf(buf, "%02d:%02d.%01d %02d:%02d.%01d ",
            now.digit[2], now.digit[1], now.digit[0] / 10,
            rap.digit[2], rap.digit[1], rap.digit[0] / 10);
        }
        break;
      case 2:                                               // カウント単位が1秒の時
        if (raphold == false) {
          sprintf(buf, "%1d:%02d:%02d         ",            // 表示は時、分、秒
            now.digit[3] % 10, now.digit[2], now.digit[1]);
        } else {
          sprintf(buf, "%1d:%02d:%02d %1d:%02d:%02d ",
            now.digit[3] % 10, now.digit[2], now.digit[1],
            rap.digit[3] % 10, rap.digit[2], rap.digit[1]);
        }
        break;
    }

    for (int d = 0; d < POINTS; d++) {
     leds[ NUM_LEDS - POINTS + d ] = CRGB::Black;  // コロンや小数点を消す
    }
    
    int p = 0;                            // 文字列中の位置
    int col = COLUMN - 1;                 // 桁位置
    char c;                               // 文字列中の文字
    for (p = 0; p < 7; p++ ) {            // 文字列の中を先頭から順番に
      c = buf[ p ];                       // 1文字を取り出す
      if (c >= '0' && c <= '9') {         // 文字が数字ならば
        leddisp[ col ] = c - '0';         // LEDに表示する数字を代入
        col--;                            // LEDの桁位置を動かす
      }
      if (c == ':' || c =='.') {          // 文字がコロンや小数点ならば
        switch (p) {
          case 1:
            leds[ NUM_LEDS - 1 ]
            = leds[ NUM_LEDS - 2 ]
            = CRGB( g, r, b );            // 最初のコロンを点灯する
            break;
          case 2:
            leds[ NUM_LEDS - 3 ]
            = leds[ NUM_LEDS - 4 ]
            = CRGB( g, r, b );            // 2番目のコロンを点灯する
            break;
          case 4:
            if (c == ':') {               // 文字はコロンか？
              leds[ NUM_LEDS - 5 ]
              = leds[ NUM_LEDS - 6 ]
              = CRGB( g, r, b );          // 3番目のコロンを点灯する
            } else {
              leds[ NUM_LEDS - 7 ]
              = CRGB( g, r, b );          // 最初の小数点を点灯する
            }
            break;
          case 5:
            leds[ NUM_LEDS - 8 ]
            = CRGB( g, r, b );          // 2番目の小数点を点灯する
            break;
        }
      }
    }
     
    int dotn = 0;                        // 全シリアル接続中の先頭からのドット番号を示す変数
    for(col = 0; col < COLUMN; col++) {
      for(int j = 0; j < SEGMENTS; j++) {  // 1桁の中のセグメントを走査
        for(int k = 0; k < DOTS; k++) {    // 1セグメントの中のドットを走査
          if( sevenseg[ leddisp[ col ] ][ j ] == 1 ) {    // 1桁の該当する7セグの中のセグメントが”ON : 1”ならば
            leds[ dotn ] = CRGB( g, r, b );      // そのドットのRGBを設定
  //          leds[dotn].fadeLightBy(220);   // そのドットの明るさを設定
            dotn++;                        // ドット番号を一つ進める
            }
          else                             // セグメントが”OFF : 0”ならば
            leds[dotn++] = CRGB::Black;    // そのドットは消して、ドット番号を一つ進める
  
  // 以下2行はLEDの色変化をさせるお遊び　どちらかのコメントを外すと色変化する
  //          color_7_fadein_fadeout();
  //          color_7_shift();
        }
      }
    }
    FastLED.show();      // LEDに表示データを送信して表示を変更する

    lcd_setline(1);                                 // カーソルを1行目に
    lcd_puts(buf);                                  // 1行分を表示
    
    updateF.sec10m = false;  // 100m秒更新が完了、フラグをクリア
    resetflag = false;   // リセットスイッチによる更新が完了、フラグをクリア
    initial = false;     // 起動時最初は終了
  }
}

// 赤、緑、青、黄、水、紫、白をそれぞれの色を順次、徐々に明るく、暗くする
#define FADE_SPEED  1
void color_7_fadein_fadeout() {
  static int colortype = 0;
    switch (colortype) {
      case 0:
        r += FADE_SPEED;  if (r >= 255) {colortype++; r = 255;}  break;
      case 1:
        r -= FADE_SPEED;  if (r <= 0) {colortype++; r = 0;}      break;
      case 2:
        g += FADE_SPEED;  if (g >= 255) {colortype++; g = 255;}  break;
      case 3:
        g -= FADE_SPEED;  if (g <= 0) {colortype++; g = 0;}      break;
      case 4:
        b += FADE_SPEED;  if (b >= 255) {colortype++; b = 255;}  break;
      case 5:
        b -= FADE_SPEED;  if (b <= 0) {colortype++; b = 0;}      break;
      case 6:
        r += FADE_SPEED; g = r;  if (r >= 255) {colortype++; r = g = 255;}  break;
      case 7:
        r -= FADE_SPEED; g = r;  if (r <= 0) {colortype++; r = g = 0;}      break;
      case 8:
        g += FADE_SPEED; b = g;  if (g >= 255) {colortype++; g = b = 255;}  break;
      case 9:
        g -= FADE_SPEED; b = g;  if (g <= 0) {colortype++; g = b = 0;}      break;
      case 10:
        b += FADE_SPEED; r = b;  if (b >= 255) {colortype++; b = r = 255;}  break;
      case 11:
        b -= FADE_SPEED; r = b;  if (b <= 0) {colortype++; b = r = 0;}      break;
      case 12:
        r += FADE_SPEED; g = b = r;  if (r >= 255) {colortype++; r = g = b = 255;} break;
      case 13:
        r -= FADE_SPEED; g = b = r;  if (r <= 0) {colortype = 0; r = g = b = 0;}   break;
    }
}

// 赤、緑、青、黄、水、紫、白のそれぞれの色を順次、徐々に切り替えていく
#define SHIFT_SPEED 1
void color_7_shift() {
  static int colornum = 0;
  switch (colornum) {
    case 0:
      r = 255; colornum++; break;
    case 1:
      r -= SHIFT_SPEED; g += SHIFT_SPEED; if (r <= 0) {r = 0; g = 255; colornum++;}; break;
    case 2:
      g -= SHIFT_SPEED; b += SHIFT_SPEED; if (g <= 0) {g = 0; b = 255; colornum++;}; break;
    case 3:
      b -= SHIFT_SPEED; r += SHIFT_SPEED; g = r; if (b <= 0) {b = 0; r = g = 255; colornum++;}; break;
    case 4:
      r -= SHIFT_SPEED; b += SHIFT_SPEED; if (r <= 0) {r = 0; b = 255; colornum++;}; break;
    case 5:
      g -= SHIFT_SPEED; r += SHIFT_SPEED; if (g <= 0) {g = 0; r = 255; colornum++;}; break;
    case 6:
      g += SHIFT_SPEED; if (g >= 255) {g = 255; colornum++;}; break;
    case 7:
      g -= SHIFT_SPEED; b = g; if (g <= 0) {g = b = 0; colornum = 1;}; break;
  }
}
